package de.iritgo.skillfull.collision;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;

import de.iritgo.simplelife.data.Tuple2;
import de.iritgo.skillfull.component.CollisionComponent;
import de.iritgo.skillfull.config.ConfigManager;
import de.iritgo.skillfull.event.EventService;

/**
 * The collision manager impl.
 * 
 * @author held
 *
 */
public class CollisionManagerImpl implements CollisionManager 
{
	/** The quad tree class */
	private QuadTree quadTree;
	
	/** The event service */
	private EventService eventService;
	
	/** The config manager */
	private ConfigManager configManager;
	
	/** Save all collisions and prevent double detection */
	private Map<String, Tuple2<CollisionComponent, CollisionComponent>> collisions;
	
	
	/** Set the event service */
	public void setEventService (EventService eventService) 
	{
		this.eventService = eventService;
	}

	/** Set the config manager */
	public void setConfigManager (ConfigManager configManager) 
	{
		this.configManager = configManager;
	}

	/** Initialize the collision manager. Called by spring. */
	public void initialize ()
	{
		quadTree = new QuadTree ();
		collisions = new HashMap<String, Tuple2<CollisionComponent, CollisionComponent>> ();
	}
	
	/**
	 * @see de.iritgo.skillfull.collision.CollisionManager#render(org.newdawn.slick.GameContainer, org.newdawn.slick.Graphics)
	 */
	public void render (GameContainer container, Graphics g)
	{
		if (configManager.functionActive("showQuadTree"))
		{
			List<QuadNode> nodes = quadTree.getQuadNodes ();
			for (QuadNode node : nodes)
			{
				
				g.drawRect(node.getX(), node.getY(), node.getWidth(), node.getHeight ());
				if (node.isSplit())
					g.drawString("--Split! S:" + node.size () + " L:" + node.getDepthLevel (), node.getX(), node.getY() + 32);
				else
				{
					if (nodes.contains(node.getParent ()))
					{
						g.drawString(node.size () + ":" + node.getDepthLevel () + ":" + node.getParent ().getDepthLevel () + "!", node.getX(), node.getY());
					}
					else
						g.drawString(node.size () + ":" + node.getDepthLevel () + ":" + node.getParent ().getDepthLevel () , node.getX(), node.getY());
				}
			}
		}
	}

	/**
	 * @see de.iritgo.skillfull.collision.CollisionManager#add(de.iritgo.skillfull.component.CollisionComponent)
	 */
	public void add (CollisionComponent collisionComponent)
	{
		quadTree.add (collisionComponent);
	}
	
	/**
	 * @see de.iritgo.skillfull.collision.CollisionManager#remove(de.iritgo.skillfull.component.CollisionComponent)
	 */
	public void remove (CollisionComponent collisionComponent)
	{
		List<QuadNode> nodes = quadTree.getQuadNodes ();
		for (QuadNode node : nodes)
		{
			node.remove(collisionComponent);
		}		
		List<String> toDelete = new LinkedList<String> ();
		for (String crashId : collisions.keySet ())
		{
			Tuple2<CollisionComponent, CollisionComponent> crash = collisions.get (crashId); 
			if ((crash.get1() == collisionComponent) || (crash.get2 () == collisionComponent))
			{
				toDelete.add(crashId);
			}
		}
		for (String crashId : toDelete)
		{
			Tuple2<CollisionComponent, CollisionComponent> crash = collisions.remove (crashId);
			crash.get1 ().markCollision(false);
			crash.get2 ().markCollision(false);
		}
	}


	/**
	 * @see de.iritgo.skillfull.collision.CollisionManager#update(org.newdawn.slick.GameContainer, int)
	 */
	public void update (GameContainer container, int delta) 
	{
		//Checked whether the collision can be resolved.
		List<String> toDelete = new LinkedList<String> ();
		for (String crashId : collisions.keySet ())
		{
			Tuple2<CollisionComponent, CollisionComponent> crash = collisions.get (crashId); 
			if (! crash.get1().touches(crash.get2()))
			{
				toDelete.add(crashId);
			}
		}
		for (String crashId : toDelete)
		{
			Tuple2<CollisionComponent, CollisionComponent> crash = collisions.remove (crashId);
			crash.get1 ().markCollision(false);
			crash.get2 ().markCollision(false);
		}

		// Check for new collisions
		List<QuadNode> nodes = new LinkedList<QuadNode> ();
		nodes.addAll (quadTree.getQuadNodes ());
		for (QuadNode node : nodes)
		{
			List<CollisionComponent> cols1 = new LinkedList<CollisionComponent> ();
			cols1.addAll (node.getCollisionComponents());
			for (CollisionComponent checkCC : cols1)
			{
				List<CollisionComponent> cols2 = new LinkedList<CollisionComponent> ();
				cols2.addAll (node.getCollisionComponents());
				for (CollisionComponent otherCC : cols2)
				{
					if (checkCC == otherCC || checkCC.getEntity().isDeactivated () || otherCC.getEntity().isDeactivated ())
						continue;
					
					if (checkCC.touches(otherCC))
					{
						String crashId = checkCC.getEntity ().getName () + otherCC.getEntity ().getName();
						checkCC.markCollision (true);
						otherCC.markCollision (true);

						if (collisions.containsKey(crashId))
						{
							// allready reported
							continue;
						}

						crashId = otherCC.getEntity ().getName() + checkCC.getEntity ().getName ();
						if (collisions.containsKey(crashId))
						{
							// allready reported
							continue;
						}
						
						collisions.put (crashId, new Tuple2<CollisionComponent, CollisionComponent>(checkCC, otherCC));
						eventService.fireEvent(new CollisionEvent (checkCC.getEntity (), otherCC.getEntity ()));
					}
				}
			}
		}
	}
}
